﻿//-----------------------------------------------------------------------
// <copyright file="MessagingService.cs" company="OOHM PROCESSAMENTO DE DADOS LTDA.">
//     Microsoft Public License (MS-PL) This license governs use of the accompanying
//     software. If you use the software, you accept this license. If you do not
//     accept the license, do not use the software. 1. Definitions The terms "reproduce,"
//     "reproduction," "derivative works," and "distribution" have the same meaning
//     here as under U.S. copyright law. A "contribution" is the original software,
//     or any additions or changes to the software. A "contributor" is any person
//     that distributes its contribution under this license. "Licensed patents"
//     are a contributor's patent claims that read directly on its contribution.
//     2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license,
//     including the license conditions and limitations in section 3, each contributor
//     grants you a non-exclusive, worldwide, royalty-free copyright license to
//     reproduce its contribution, prepare derivative works of its contribution,
//     and distribute its contribution or any derivative works that you create.
//     (B) Patent Grant- Subject to the terms of this license, including the license
//     conditions and limitations in section 3, each contributor grants you a non-exclusive,
//     worldwide, royalty-free license under its licensed patents to make, have
//     made, use, sell, offer for sale, import, and/or otherwise dispose of its
//     contribution in the software or derivative works of the contribution in
//     the software. 3. Conditions and Limitations (A) No Trademark License- This
//     license does not grant you rights to use any contributors' name, logo, or
//     trademarks. (B) If you bring a patent claim against any contributor over
//     patents that you claim are infringed by the software, your patent license
//     from such contributor to the software ends automatically. (C) If you distribute
//     any portion of the software, you must retain all copyright, patent, trademark,
//     and attribution notices that are present in the software. (D) If you distribute
//     any portion of the software in source code form, you may do so only under
//     this license by including a complete copy of this license with your distribution.
//     If you distribute any portion of the software in compiled or object code
//     form, you may only do so under a license that complies with this license.
//     (E) The software is licensed "as-is." You bear the risk of using it. The
//     contributors give no express warranties, guarantees or conditions. You may
//     have additional consumer rights under your local laws which this license
//     cannot change. To the extent permitted under your local laws, the contributors
//     exclude the implied warranties of merchantability, fitness for a particular
// </copyright>
//-----------------------------------------------------------------------
namespace AbstractSaaSTemplate.Application.Services{    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using AbstractSaaSTemplate.Application.Localization;
    using AbstractSaaSTemplate.Application.Templates;
    using AbstractSaaSTemplate.Domain.Exceptions;
    using AbstractSaaSTemplate.Domain.Models.Entities;
    using AbstractSaaSTemplate.Domain.Models.ValueObjects;
    using AbstractSaaSTemplate.Domain.Repositories;
    using AbstractSaaSTemplate.Domain.Services;

    /// <summary>
    /// Implements 'IMessagingService' and all its contract, providing
    /// intelligent services to 'Membership' concern of the business.
    /// </summary>
    public sealed class MessagingService : IMessagingService
    {
        /// <summary>
        /// Initializes a new instance of the MessagingService class.
        /// </summary>
        /// <param name="repositoryForTemplates">The repository to opreate 'Templates'.</param>
        /// <param name="repositoryForTenancy">The repository to operate 'Tenancy'.</param>
        /// <param name="repositoryForMessages">The repository to operate 'Messages'.</param>
        /// <param name="repositoryForMembership">Teh repository to operate 'Membership'.</param>
        public MessagingService(
            ITemplatesRepository repositoryForTemplates,
            ITenancyRepository repositoryForTenancy,
            IMessagesRepository repositoryForMessages,
            IMembershipRepository repositoryForMembership)
        {
            this.RepositoryForTemplates = repositoryForTemplates;
            this.RepositoryForTenancy = repositoryForTenancy;
            this.RepositoryForMessages = repositoryForMessages;
            this.RepositoryForMembership = repositoryForMembership;
        }

        /// <summary>
        /// Gets the repository to operate 'Templates'.
        /// </summary>
        public ITemplatesRepository RepositoryForTemplates { get; private set; }

        /// <summary>
        /// Gets the repository to opreate 'Messages'.
        /// </summary>
        public IMessagesRepository RepositoryForMessages { get; private set; }

        /// <summary>
        /// Gets the repository to operate 'Tenancy'.
        /// </summary>
        public ITenancyRepository RepositoryForTenancy { get; private set; }

        /// <summary>
        /// Gets or sets the repository to operate 'Membership'.
        /// </summary>
        public IMembershipRepository RepositoryForMembership { get; set; }

        /// <summary>
        /// Generates a welcome message with login instructions to the tenant administrator.
        /// </summary>
        /// <param name="senderUserId">The id of the user who is sending the message.</param>
        /// <param name="receiverTenantId">The id of the tenant who will receive the message.</param>
        /// <param name="receiverUserId">The id of the user who will receive the message.</param>
        /// <param name="callbackUrl">The url that drives the user back to the service from the e-mail.</param>
        /// <exception cref="MessagingUserCouldNotBeFoundException">Raised whenever the 'senderUserId' or the 'receiverUserId' does not find a matching User.</exception>
        /// <exception cref="MessagingTenantCouldNotBeFoundException">Raised whenever the 'receiverTenantId' does not find a matching Tenant.</exception>
        public void SendWelcomeMessageForTenant(
            long senderUserId,
            long receiverTenantId,
            long receiverUserId,
            Uri callbackUrl)
        {
            this.SendWelcomeMessage(
                senderUserId,
                receiverTenantId,
                null,
                receiverUserId,
                callbackUrl);
        }

        /// <summary>
        /// Generates a welcome message with login instructions to the account administrator.
        /// </summary>
        /// <param name="senderUserId">The id of the user who is sending the message.</param>
        /// <param name="receiverAccountId">The id of the tenant who will receive the message.</param>
        /// <param name="receiverUserId">The id of the user who will receive the message.</param>
        /// <param name="callbackUrl">The url that drives the user back to the service from the e-mail.</param>
        /// <exception cref="MessagingUserCouldNotBeFoundException">Raised whenever the 'senderUserId' or the 'receiverUserId' does not find a matching User.</exception>
        /// <exception cref="MessagingTenantCouldNotBeFoundException">Raised whenever the 'receiverAccountId' does not find a matching Account.</exception>
        public void SendWelcomeMessageForAccount(
            long senderUserId,
            long receiverAccountId,
            long receiverUserId,
            Uri callbackUrl)
        {
            this.SendWelcomeMessage(
                senderUserId,
                null,
                receiverAccountId,
                receiverUserId,
                callbackUrl);
        }
        
        /// <summary>
        /// Generates a new message from the template
        /// </summary>
        /// <typeparam name="TCustomData">The type of the custom data of the message (can be any value object inherited from MessageCustomData).</typeparam>
        /// <typeparam name="TDestination">The type of the destination (can be any value object inherited from MessageDestination).</typeparam>
        /// <param name="templateKey">The template key which will be processed.</param>
        /// <param name="fromAddress">The origin of the message.</param>
        /// <param name="toAddressList">The list of receivers for the message.</param>
        /// <param name="parameters">The parameters which will be replaced from the template to generate the message.</param>
        /// <returns>Returns a message created from the template.</returns>
        public Message<TCustomData, TDestination> NewMessageFromTemplate<TCustomData, TDestination>(
            string templateKey,
            TDestination fromAddress,
            IEnumerable<TDestination> toAddressList,
            IDictionary<string, string> parameters)
            where TCustomData : MessageCustomData
            where TDestination : MessageDestination
        {
            return this.NewMessageFromTemplate<TCustomData, TDestination>(
                default(long),
                templateKey,
                fromAddress,
                toAddressList,
                parameters);
        }

        /// <summary>
        /// Generates a new message from the template
        /// </summary>
        /// <typeparam name="TCustomData">The type of the custom data of the message (can be any value object inherited from MessageCustomData).</typeparam>
        /// <typeparam name="TDestination">The type of the destination (can be any value object inherited from MessageDestination).</typeparam>
        /// <param name="tenantId">The id of the tenant for which we shall attempt to find a customised template.</param>
        /// <param name="templateKey">The template key which will be processed.</param>
        /// <param name="fromAddress">The origin of the message.</param>
        /// <param name="toAddressList">The list of receivers for the message.</param>
        /// <param name="parameters">The parameters which will be replaced from the template to generate the message.</param>
        /// <returns>Returns a message created from the template.</returns>
        public Message<TCustomData, TDestination> NewMessageFromTemplate<TCustomData, TDestination>(
            long tenantId,
            string templateKey,
            TDestination fromAddress,
            IEnumerable<TDestination> toAddressList,
            IDictionary<string, string> parameters)
            where TCustomData : MessageCustomData
            where TDestination : MessageDestination
        {
            var template = null as Template;
            if (tenantId != default(long))
            {
                template = this.RepositoryForTemplates.GetTemplateByKey<TCustomData>(
                    tenantId,
                    templateKey);
            }

            template = this.RepositoryForTemplates.GetTemplateByKey<TCustomData>(templateKey);

            return this.NewMessageFromTemplate<TCustomData, TDestination>(
                template,
                fromAddress,
                toAddressList,
                parameters);
        }

        /// <summary>
        /// Generates a new message from the template
        /// </summary>
        /// <typeparam name="TCustomData">The type of the custom data of the message (can be any value object inherited from MessageCustomData).</typeparam>
        /// <typeparam name="TDestination">The type of the destination (can be any value object inherited from MessageDestination).</typeparam>
        /// <param name="template">The template which will be processed.</param>
        /// <param name="fromAddress">The origin of the message.</param>
        /// <param name="toAddressList">The list of receivers for the message.</param>
        /// <param name="parameters">The parameters which will be replaced from the template to generate the message.</param>
        /// <returns>Returns a message created from the template.</returns>
        public Message<TCustomData, TDestination> NewMessageFromTemplate<TCustomData, TDestination>(
            Template template,
            TDestination fromAddress,
            IEnumerable<TDestination> toAddressList,
            IDictionary<string, string> parameters)
                where TCustomData : MessageCustomData
                where TDestination : MessageDestination
        {
            if (template == null)
            {
                throw new ArgumentNullException("template");
            }

            if (fromAddress == null)
            {
                throw new ArgumentNullException("fromAddress");
            }

            if (toAddressList == null || toAddressList.Count() == 0)
            {
                throw new ArgumentNullException("toAddressList");
            }
            
            // we here create the message that will be generated
            // from the template by copying it and replacing the
            // template paramter tokens.
            var message = new Message<TCustomData, TDestination>();
            
            // initializes the destination
            message.FromAddress = fromAddress;
            message.ToAddressList = toAddressList;
            
            // we copy all data from the template into the message
            // this is done to avoid that the template customData
            // gets change by reference.
            var typeOfCustomData = typeof(TCustomData);
            var flagsForCustomDataProperties = BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty;
            foreach (var property in typeOfCustomData.GetProperties(flagsForCustomDataProperties))
            {
                // we retrieve the source value from the template using
                // reflection for each public instance get properties available
                var sourceValue = property.GetValue(template.CustomData, null);

                // if the sourceValue is string, it's suitable for token
                // replacement.  while copying each data, we shall also
                // replace all tokens available in the parameters dictionary
                // by its value.
                var parsedSource = sourceValue as string;
                if (!string.IsNullOrWhiteSpace(parsedSource))
                {
                    if (!string.IsNullOrWhiteSpace(parsedSource) &&
                        parameters != null &&
                        parameters.Count != 0)
                    {
                        foreach (var parameter in parameters)
                        {
                            var rawKey = parameter.Key.TrimStart('{').TrimEnd('}');
                            string tokenKey = string.Concat("{", rawKey, "}");
                            parsedSource = parsedSource.Replace(tokenKey, parameter.Value);
                        }
                    }
                }

                // finally, after the replacementes, we set the value
                // with the replaced tokens to the property of the message
                if (!string.IsNullOrWhiteSpace(parsedSource))
                {
                    property.SetValue(
                        message.CustomData,
                        parsedSource,
                        null);
                }
            }

            // now we return the message generated from this procedure.
            return message;
        }

        /// <summary>
        /// Generates a welcome message with login instructions to the tenant administrator.
        /// </summary>
        /// <param name="senderUserId">The id of the user.</param>
        /// <param name="receiverTenantId">The id of the tenant (if this e-mail's destination is a tenant)</param>
        /// <param name="receiverAccountId">The id of the account (if this e-mail's destination is a tenant)</param>
        /// <param name="receiverUserId">The id of the tenant's or account's administrator..</param>
        /// <param name="serviceUrl">The callback url for the service.</param>
        private void SendWelcomeMessage(
            long senderUserId,
            long? receiverTenantId,
            long? receiverAccountId,
            long receiverUserId,
            Uri serviceUrl)
        {
            if (serviceUrl == null)
            {
                throw new ArgumentNullException("serviceUrl");
            }

            if (!receiverTenantId.HasValue && !receiverAccountId.HasValue)
            {
                throw new ArgumentException(string.Concat(
                    CultureInfo.CurrentUICulture,
                    Messages.EitherTheParameterXOrTheParameterYMustReceiveAValue,
                    "tenantId",
                    "accountId"));
            }

            var sender = this.RepositoryForMembership.GetUserById(senderUserId);
            if (sender == null)
            {
                throw new MessagingUserCouldNotBeFoundException(
                    senderUserId,
                    string.Format(CultureInfo.CurrentUICulture, Messages.TheUserWithIdXWasNotFound, senderUserId));
            }

            var receiver = this.RepositoryForMembership.GetUserById(receiverUserId);
            if (sender == null)
            {
                throw new MessagingUserCouldNotBeFoundException(
                    receiverUserId,
                    string.Format(CultureInfo.CurrentUICulture, Messages.TheUserWithIdXWasNotFound, receiverUserId));
            }

            var companyName = string.Empty;
            var companyDisplayName = string.Empty;

            if (receiverTenantId.HasValue)
            {
                var tenant = this.RepositoryForTenancy.GetTenantById(receiverTenantId.Value);
                if (tenant == null)
                {
                    throw new MessagingTenantCouldNotBeFoundException(
                        receiverTenantId.Value,
                        string.Format(CultureInfo.CurrentUICulture, Messages.TheTenantWithIdXWasNotFoud, receiverTenantId.Value));
                }

                companyName = tenant.CompanyName;
                companyDisplayName = tenant.DisplayName;
            }

            if (receiverAccountId.HasValue)
            {
                var account = this.RepositoryForMembership.GetAccountById(receiverAccountId.Value);
                if (account == null)
                {
                    throw new MessagingAccountCouldNotBeFoundException(
                        receiverAccountId.Value,
                        string.Format(CultureInfo.CurrentUICulture, Messages.TheAccountWithIdXWasNotFound, receiverAccountId.Value));
                }

                companyName = account.Name;
                companyDisplayName = account.DisplayName;
            }

            var parameters = new Dictionary<string, string>()
            {
                { "{app-name}", Metadata.ApplicationName },
                { "{receiver-name}", receiver.FullName },
                { "{client-company-display-name}", companyDisplayName },
                { "{client-company-name}", companyName },
                { "{service-url}", serviceUrl.AbsoluteUri },
                { "{client-administrator-login}", receiver.Credentials.Login },
                { "{client-administrator-password}", receiver.Credentials.RetrievePassword() },
                { "{sender-fullname}", sender.FullName }
            };

            var message = NewMessageFromTemplate<EmailMessage, EmailAddress>(
                EmailTemplates.WelcomeMessageForAdministrator,
                new EmailAddress(sender.FullName, sender.EmailAddress),
                new[] { new EmailAddress(receiver.FullName, receiver.EmailAddress) },
                parameters);

            this.RepositoryForMessages.ScheduleMessageForDispatch(message);
        }
    }
}


